package net.gdface.facelog;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.ImmutableNode;

import com.google.common.base.Functions;
import com.google.common.base.Predicates;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.net.HostAndPort;

import net.gdface.facelog.DtalkServiceMenu.ExtractFeatureBaseFaceApi;
import net.gdface.facelog.common.FaceApiCmdAdapter;
import net.gdface.image.ImageErrorException;
import net.gdface.sdk.BaseFaceApiLocal;
import net.gdface.sdk.CapacityFieldConstant;
import net.gdface.sdk.ContextLoader;
import net.gdface.sdk.FaceApi;
import net.gdface.sdk.FaceApiContext;
import net.gdface.sdk.FseResult;
import net.gdface.sdk.NotFaceDetectedException;
import net.gdface.sdk.fse.FeatureSe;
import net.gdface.sdk.fse.thrift.FeatureSeThriftClient;
import net.gdface.sdk.thrift.FaceApiThriftClient;
import net.gdface.thrift.ClientFactory;
import net.gdface.utils.SimpleTypes;

import static com.google.common.base.Preconditions.*;
import static net.gdface.facelog.FeatureConfig.FEATURE_CONFIG;
import static com.google.common.base.MoreObjects.firstNonNull;
import static net.gdface.utils.ConditionChecks.checkTrue;
import static net.gdface.utils.ConditionChecks.checkNotNull;
/**
 * FaceApi RPC服务管理
 * @author guyadong
 *
 */
class FaceApiServiceManagement implements ServiceConstant,CapacityFieldConstant {
	private static class SingletonTimer{
		private static final Timer TIMER = new Timer("FaceApiServiceManagement-timer",true);
	}
	private BiMap<String, HostAndPort> hosts = Maps.synchronizedBiMap(HashBiMap.<String, HostAndPort>create());
	/** 不能访问的服务 */ 
	private Map<String, HostAndPort> unreachableHosts = Maps.newConcurrentMap();
	private Map<String, FaceApi> faceApiInstances = Maps.newHashMap();
	private Map<String, FeatureSe> fseInstances = Maps.newConcurrentMap();
	private Map<String, FseEngine> fseEngines = Maps.newConcurrentMap();
	private Map<String, String> config;
	private final DtalkServiceTaskDispatcher dt;
	private final RedisManagement rm;
	private final DaoManagement dm;
	/** 定时任务间隔(秒)  */
	private final long timerPeriod;
	/** 默认相似度阀值  */
	private float defaultSimThreshold;

	FaceApiServiceManagement(DtalkServiceTaskDispatcher dt, RedisManagement rm, DaoManagement dm) {
		this.dt = dt;
		this.rm = rm;
		this.dm = dm;
		this.timerPeriod = 1000*CONFIG.getInt(FACEAPI_SERVICEMANAGEMENT_TIMERPERIODSEC, 
				DEFAULT_FACEAPI_SERVICEMANAGEMENT_TIMERPERIOD);
		this.defaultSimThreshold = CONFIG.getFloat(FACEAPI_SERVICEMANAGEMENT_SIMTHRESHOLD, 
				DEFAULT_FACEAPI_SERVICEMANAGEMENT_SIMTHRESHOLD);
	}
	private boolean exists(String sdkVersion){
		return faceApiInstances.containsKey(sdkVersion);
	}
	
	/**
	 * 初始化指定算法的RPC服务相关对象
	 * @param sdkVersion
	 * @param hostAndPort
	 * @return 初始化成功返回初始化对象数组,否则返回{@code null}
	 */
	private Object[] doInit(String sdkVersion,HostAndPort hostAndPort){
		// 测试服务是否可连接
		if(ClientFactory.testConnect(hostAndPort.getHost(),hostAndPort.getPort(), 0)){
			Object[] initObjs = new Object[5];
			FaceApiThriftClient faceapi = new FaceApiThriftClient(hostAndPort);
			initObjs[0] = faceapi;
			String sv = faceapi.sdkCapacity().get(C_SDK_VERSION);
			// FaceApi服务的SDK版本号必须匹配
			checkArgument(sdkVersion.equals(sv),
					"MISMATCH sdkVersion％s VS %s with %s",
					sdkVersion, sv, faceapi);
			initObjs[1] = rm.sdkTaskQueueOf(TASK_FACEAPI_BASE, sdkVersion);
			initObjs[2] = rm.sdkTaskQueueOf(TASK_FEATURE_BASE, sdkVersion);
			logger.info("create FSE client:{} for {}",hostAndPort,sdkVersion);
			FeatureSeThriftClient fse = new FeatureSeThriftClient(hostAndPort);					
			initObjs[3] = fse;
			FseEngine fseEngine = new FseEngine(fse, sdkVersion);
			try {
				fseEngine.init();
			} catch (Exception e) {
				// 初始化过程中出错则继续循环
				logger.error("{}:{}",e.getClass().getSimpleName(),e.getMessage());
				return null;
			}
			// 初始化成功加入FSE 引擎表中
			initObjs[4] = fseEngine; 
			return initObjs;
		}
		logger.warn("NOT CONNECTABLE FaceApi/FSE service:{}",hostAndPort);
		return null;
	}
	private Object[] update(Object[] objs,String sdkVersion){
		if(objs != null){
			checkArgument(!Iterables.tryFind(Arrays.asList(objs), Predicates.isNull()).isPresent(),"objs has null elements");
			if(sdkVersion == null){
				sdkVersion =  ((FaceApi) objs[0]).sdkCapacity().get(C_SDK_VERSION);
			}
			faceApiInstances.put(sdkVersion, (FaceApi) objs[0]);
			// faceapi实例绑定到命令执行器,并将任务分发器注册到的FACEAPI任务队列
			FaceApiCmdAdapter.INSTANCE.bindFaceApi((FaceApi) objs[0]);
			ExtractFeatureBaseFaceApi.ADAPTER.bindFaceApi((FaceApi) objs[0]);
			dt.register((String) objs[1]);
			dt.register((String) objs[2]);
			fseInstances.put(sdkVersion, (FeatureSe) objs[3]);
			// 初始化成功加入FSE 引擎表中
			fseEngines.put(sdkVersion,(FseEngine) objs[4]);
		}
		return objs;
	}
	/**
	 * 初始化指定算法的RPC服务相关对象<br>
	 * 由{@link #doInit(String, HostAndPort)}执行初始化，初始化成功后再将对应的对象一个个保存到对应的map,
	 * 这样可以实现类似事务机制，确保所有初始化操作后统一保存到map,保证数据的一致性
	 * @param sdkVersion
	 * @param hostAndPort
	 * @return 初始化成功返回{@code true},否则返回{@code false}
	 */
	private synchronized boolean initService(String sdkVersion,HostAndPort hostAndPort){
		Object[] objs = doInit(sdkVersion,hostAndPort);
		if(objs != null){
			try {
				hosts.put(sdkVersion, hostAndPort);
			} catch (IllegalArgumentException e) {
				// hostAndPort 已经被另一个SDK VERSION占用
				throw new IllegalStateException(
						String.format("EXIST DUPLICATED HOST  DEFINITION :%s %s",
								hosts.inverse().get(hostAndPort),
								hosts));
			}
		}
		return update(objs,sdkVersion) != null;
	}
	
	/**
	 * 初始化指定算法相关对象
	 * @param local 本地算法实例
	 * @return 初始化成功返回初始化对象数组
	 */
	private Object[] doInit(BaseFaceApiLocal local){
		Object[] initObjs = new Object[4];
		String sdkVersion = local.sdkCapacity().get(C_SDK_VERSION);
		BaseFaceApiLocal faceapi = local;
		initObjs[0] = faceapi;
		initObjs[1] = rm.sdkTaskQueueOf(TASK_FACEAPI_BASE, sdkVersion);

		logger.info("get FSE instance for {}",sdkVersion);
		FeatureSe fse =BaseFaceApiLocal.getFeatureSeInstance(local);	
		if(fse == null){
			return null;
		}
		initObjs[2] = fse;
		FseEngine fseEngine = new FseEngine(fse, sdkVersion);
		try {
			fseEngine.init();
		} catch (Exception e) {
			// 初始化过程中出错则继续循环
			logger.error("{}:{}",e.getClass().getSimpleName(),e.getMessage());
			return null;
		}
		// 初始化成功加入FSE 引擎表中
		initObjs[3] = fseEngine; 
		return initObjs;
	}
	
	/**
	 * 通过 FaceApi 的应用上下文({@link ContextLoader})获取可用的本地 FaceApi 实例并初始化
	 */
	private void initLocalInstance(){
		for(FaceApiContext context:ContextLoader.getInstance().CONTEXTS){
			FaceApi instance = ContextLoader.ContextField.INSTANCE.from(context);
			if(instance instanceof BaseFaceApiLocal){
				Object[] objs = doInit( (BaseFaceApiLocal)instance);
				update(objs, null);
			}
		}
	}
	/**
	 * 对象初始化
	 */
	void init(){
		initLocalInstance();
		// 加载所有 faceapi 服务配置
		List<HierarchicalConfiguration<ImmutableNode>> childs = CONFIG.childConfigurationsAt(PREFIX_FACEAPI_SERVICE);
		for(HierarchicalConfiguration<ImmutableNode> c:childs){
			String sdkVersion = c.getRootElementName();
			// 检查sdk_version是否有效
			checkArgument(FEATURE_CONFIG.validateSdkVersion(sdkVersion), 
					"UNSUPPORTED SDK Version [%s]",sdkVersion);
			HostAndPort hostAndPort = HostAndPort.fromParts(
					c.getString("host",DEFAULT_FACEAPI_SERVICE_HOST), 
					c.getInt("port",DEFAULT_FACEAPI_SERVICE_PORT));

			if(c.getBoolean("enable",Boolean.FALSE)){
				if(!exists(sdkVersion) && !initService(sdkVersion,hostAndPort)){
					unreachableHosts.put(sdkVersion, hostAndPort);
				}
			}
		}
		if(!unreachableHosts.isEmpty()){
			// 启动定时任务尝试重新初始化连接不可访问的主机
			SingletonTimer.TIMER.schedule(new UnreachableHostsReinitTask(), timerPeriod,timerPeriod);
		}
		config = Collections.unmodifiableMap(Maps.transformValues(hosts, Functions.toStringFunction()));
	}
	Map<String, String> getConfig(){
		return config;
	}
	/**
	 * 执行特征码内存搜索引擎(feature search engine)的 1:N特征搜索,识别照片中的人脸<br>
	 * 尝试使用当前sdkVersion指定的算法实例对指定的人脸图像进行识别,返回对应的用户ID(fl_person表的主键)
	 * @param sdkVersion SDK版本号
	 * @param imageData 人脸图像数据(jpg,png,bmp)
	 * @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
	 * @return 如果识别成功返回用户ID,否则返回无效ID(-1)
	 * @throws FaceApiRuntimeException 识别时产生的异常
	 */
	int recognize(String sdkVersion, byte[] imageData, Float threshold) throws FaceApiRuntimeException{
		checkTrue(faceApiInstances.containsKey(
				checkNotNull(sdkVersion,FaceApiRuntimeException.class,"sdkVersion is null")),
				FaceApiRuntimeException.class,
				"UNSUPPORTED SDK_VERSION [%s]", sdkVersion);
		try {
			FseResult[] results = faceApiInstances.get(sdkVersion).searchFaces(imageData, null, 
					firstNonNull(threshold, defaultSimThreshold), 5);
			for(FseResult r : results){
				Integer personId = r.appIntId();
				if(personId != null){
					return personId;
				}
			}
		} catch (NotFaceDetectedException e) {
			throw new FaceApiRuntimeException(e);
		} catch (ImageErrorException e) {
			throw new FaceApiRuntimeException(e);
		} catch (RuntimeException e) {			
			throw new FaceApiRuntimeException(e);
		} 
		return -1;
	}
	/**
	 * 执行特征码内存搜索引擎(feature search engine)的 1:N特征搜索,识别照片中的人脸<br>
	 * 尝试使用当前加载的所有算法实例对指定的人脸图像进行识别,返回对应的用户ID(fl_person表的主键)
	 * @param imageData 人脸图像数据(jpg,png,bmp)
	 * @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
	 * @return 如果识别成功返回用户ID,否则返回无效ID(-1)
	 * @throws FaceApiRuntimeException 识别时产生的异常
	 */
	int recognize(byte[] imageData,Float threshold) throws FaceApiRuntimeException{
		checkTrue(!faceApiInstances.isEmpty(), FaceApiRuntimeException.class, "NOT AVAILABLE FaceApi INSTANCE");
		int id = -1;
		FaceApiRuntimeException last = null;
		for(String sdkVersion : faceApiInstances.keySet()){
			try {
				if((id = recognize(sdkVersion,imageData,threshold)) > 0){
					return id;
				}
			} catch (FaceApiRuntimeException e) {
				last = e;
				if(e.getCause() instanceof NotFaceDetectedException){
					// 未检测到人脸则可以尝试下一个算法
					continue;
				} else if(SimpleTypes.isNetworkError(e.getCause())){
					// 服务连接不上可以尝试下一个算法
					continue;
				}
				logger.warn("recognize with {} ERROR {}:{}",sdkVersion,e.getClass().getSimpleName(),e.getMessage());
				throw e;
			}
		}
		if(last != null){
			logger.warn("recognize ERROR {}:{}",last.getClass().getSimpleName(),last.getMessage());
			throw last;
		}
		return id;
	}

	/**
	 * 识别照片中的人脸,识别后判断是否可以在指定设备通行<br>
	 * @param imageData 人脸图像数据(jpg,png,bmp)
	 * @param threshold 人脸特征比对的相似度阀值,为{@code null}使用默认值
	 * @param deviceId 设备ID
	 * @param sdkVersion SDK版本号,为{@code null}时尝试所有算法
	 * @return 如果识别的用户允许在指定设备通过返回用户ID,否则返回无效ID(-1)
	 * @throws FaceApiRuntimeException 识别时产生的异常
	 * @see #recognize(byte[], Float)
	 */
	int permit(byte[] imageData, Float threshold, int deviceId, String sdkVersion) throws FaceApiRuntimeException{
		int personId ;
		if(sdkVersion == null){
			personId = recognize(imageData,threshold);
		}else{
			personId = recognize(sdkVersion,imageData,threshold);
		}
		if(personId > 0){
			if(!dm.daoIsPermit(personId,deviceId)){
				return -1;
			}
		}	
		return personId;
	}
	/**
	 * 对不能访问主机尝试再初始化的定时任务
	 * @author guyadong
	 *
	 */
	private class UnreachableHostsReinitTask extends TimerTask{

		@Override
		public void run() {
			for(Iterator<Entry<String, HostAndPort>> itor = unreachableHosts.entrySet().iterator();itor.hasNext();){
				Entry<String, HostAndPort> entry = itor.next();
				String sdkVersion = entry.getKey();
				HostAndPort hostAndPort = entry.getValue();
				if(initService(sdkVersion,hostAndPort)){
					itor.remove();
				}
			}
			// 终止定时任务
			if(unreachableHosts.isEmpty()){
				SingletonTimer.TIMER.cancel();
			}
		}
	}
}
